﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "QtBalloonTip.h"
#include "GbmpNew.h"

#include <QLabel>
#include <QVBoxLayout>
#include <QApplication>
#include <QDesktopWidget>
#include <QPainter>
#include <QBitmap>
#include <QMouseEvent>
#include "BalloonTipManager.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

namespace
{

const char* DefaultIconPath = ":/images/notification/error.png";
const int BorderWidth = 1;
const int DefaultTimeout = 10000;
    
}

namespace gcmp
{

QtBalloonTip::Parameter::Parameter()
    : useIcon(true)
    , icon(QPixmap(DefaultIconPath))
    , useTitle(false)
    , title(L"")
    , message(L"")
    , timeout(DefaultTimeout)
    , showArrow(true)
{
}

QtBalloonTip::QtBalloonTip(QWidget* pOwnerWidget, const Parameter& parameter)
    : QWidget(pOwnerWidget, Qt::ToolTip)
    , m_pOwnerWidget(pOwnerWidget)
    , m_parameter(parameter)
    , m_timerId(-1)
    , m_borderPixmap()
    , m_ownerWindowAboutToDeactivate(false)
{
    if (m_parameter.timeout <= 0)
        m_parameter.timeout = DefaultTimeout;

    // icon
    QLabel* pIconLabel = nullptr;
    if (m_parameter.useIcon)
    {
        // useIcon但icon是空，使用默认的
        if (m_parameter.icon.isNull())
            m_parameter.icon.load(DefaultIconPath);
        pIconLabel = NEW_AS_QT_CHILD(QLabel, this);
        DBG_WARN_AND_RETURN_VOID_UNLESS(pIconLabel != nullptr, L"pIconLabel为空",L"GDMPLab",L"2024-03-30");
        pIconLabel->setPixmap(m_parameter.icon);
        pIconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        pIconLabel->setMargin(2);
    }

    // title
    QLabel* pTitleLabel = nullptr;
    if (m_parameter.useTitle)
    {
        pTitleLabel = NEW_AS_QT_CHILD(QLabel, QString::fromStdWString(m_parameter.title), this);
        DBG_WARN_AND_RETURN_VOID_UNLESS(pTitleLabel != nullptr, L"pTitleLabel为空",L"GDMPLab",L"2024-03-30");
        QFont titleFont = pTitleLabel->font();
        titleFont.setBold(true);
        pTitleLabel->setFont(titleFont);
        pTitleLabel->setTextFormat(Qt::PlainText);
    }

    // message
    QLabel* pMessageLabel = NEW_AS_QT_CHILD(QLabel, QString::fromStdWString(m_parameter.message), this);
    DBG_WARN_AND_RETURN_VOID_UNLESS(pMessageLabel != nullptr, L"pMessageLabel为空",L"GDMPLab",L"2024-03-30");
    
    DBG_WARN_AND_RETURN_VOID_UNLESS(QApplication::desktop() != nullptr, L"QApplication::desktop()为空",L"GDMPLab",L"2024-03-30");
    int limit = QApplication::desktop()->availableGeometry(pMessageLabel).size().width() / 3;   // 大于屏幕1/3
    if (pMessageLabel->sizeHint().width() > limit)
    {
        pMessageLabel->setWordWrap(true);
    }

    // 1. message...
    // 2. title
    //    message...
    // 3. icon message...
    // 4. icon title
    //    message........
    QHBoxLayout* pHLayout = NEW_AS_QT_LAYOUT(QHBoxLayout);
    if (pIconLabel)
        pHLayout->addWidget(pIconLabel);
    if (pTitleLabel)
    {
        pHLayout->addWidget(pTitleLabel);
    }
    else
    {
        pHLayout->addWidget(pMessageLabel);
    }

    QVBoxLayout* pVLayout = NEW_AS_QT_LAYOUT(QVBoxLayout);
    DBG_WARN_AND_RETURN_VOID_UNLESS(pVLayout != nullptr, L"pVLayout为空",L"GDMPLab",L"2024-03-30");
    pVLayout->setContentsMargins(0, 0, 0, 0);
    pVLayout->setSpacing(2);
    pVLayout->setMargin(2);
    pVLayout->addLayout(pHLayout);
    if (pTitleLabel)
        pVLayout->addWidget(pMessageLabel);

    setLayout(pVLayout);

    QPalette pal = palette();
    pal.setColor(QPalette::Window, QColor(0xff, 0xff, 0xe1));
    pal.setColor(QPalette::WindowText, Qt::black);
    setPalette(pal);

    if (m_pOwnerWidget)
    {
        m_pOwnerWidgetSignalBlocker = NEW_AS_OWNER_PTR(QSignalBlocker, pOwnerWidget);
        DBG_WARN_AND_RETURN_VOID_UNLESS(m_pOwnerWidgetSignalBlocker != nullptr, L"m_pOwnerWidgetSignalBlocker为空",L"GDMPLab",L"2024-03-30");
        m_pOwnerWidgetSignalBlocker->unblock();

        QWidget* pTopLevelWidget = m_pOwnerWidget->topLevelWidget();
        if (pTopLevelWidget)
            pTopLevelWidget->installEventFilter(this);
    }

    setAttribute(Qt::WA_DeleteOnClose);
}

QtBalloonTip::~QtBalloonTip()
{
    BalloonTipManager::GetInstance().DetachBalloon();
}

void QtBalloonTip::SetBalloonTip(const Parameter& parameter)
{
    m_parameter = parameter;
}

QWidget* QtBalloonTip::GetOwnerWidget() const
{
    return m_pOwnerWidget;
}

void QtBalloonTip::ShowBalloon()
{
    if (!m_pOwnerWidget)
        return;

    QPoint pos = m_pOwnerWidget->mapToGlobal(m_pOwnerWidget->rect().center());
    QPainterPath balloonPath = GetBalloonPath(pos);
    QSize balloonSize = sizeHint();

    // 设置蒙版
    QBitmap bitmap = QBitmap(balloonSize);
    bitmap.fill(Qt::color0);
    QPainter maskPainter(&bitmap);
    maskPainter.setPen(QPen(Qt::color1, BorderWidth));
    maskPainter.setBrush(QBrush(Qt::color1));
    maskPainter.drawPath(balloonPath);
    setMask(bitmap);

    // 描边
    m_borderPixmap = QPixmap(balloonSize);
    QPainter borderPainter(&m_borderPixmap);
    borderPainter.setPen(QPen(palette().color(QPalette::Window).darker(160), BorderWidth));
    borderPainter.setBrush(palette().color(QPalette::Window));
    borderPainter.drawPath(balloonPath);

    m_timerId = startTimer(m_parameter.timeout);
    show();
}

bool QtBalloonTip::eventFilter(QObject* pWatched, QEvent* pEvent)
{
    if (pWatched && m_pOwnerWidget && (pWatched == m_pOwnerWidget->topLevelWidget()))
    {
        const QEvent::Type type = pEvent->type();
        if ((type == QEvent::Move) || (type == QEvent::Resize))
        {
            ShowBalloon();
        }
        else if ((type == QEvent::WindowDeactivate) || (type == QEvent::Hide))
        {
            m_pOwnerWidgetSignalBlocker->reblock();
            hide();
        }
        else if (type == QEvent::WindowActivate)
        {
            m_pOwnerWidgetSignalBlocker->unblock();     // unblock恢复QSignalBlocker创建前的状态
            ShowBalloon();
        }
    }

    return QWidget::eventFilter(pWatched, pEvent);
}

void QtBalloonTip::paintEvent(QPaintEvent* pEvent)
{
    Q_UNUSED(pEvent);
    QPainter painter(this);
    painter.drawPixmap(rect(), m_borderPixmap);
}

void QtBalloonTip::timerEvent(QTimerEvent* pEvent)
{
    if (pEvent->timerId() == m_timerId)
    {
        killTimer(m_timerId);
        if (!underMouse())
            close();
        return;
    }
    QWidget::timerEvent(pEvent);
}

QPainterPath QtBalloonTip::GetBalloonPath(const QPoint& pos)
{
    QRect screenRect = QApplication::desktop()->screenGeometry(pos);
    QSize sh = sizeHint();
    const int ah = 18, ao = 18, aw = 18, rc = 7;
    bool arrowAtTop = (pos.y() + sh.height() + ah < screenRect.height());
    bool arrowAtLeft = (pos.x() + sh.width() - ao < screenRect.width());
    setContentsMargins(BorderWidth + 3, BorderWidth + (arrowAtTop ? ah : 0) + 2, BorderWidth + 3, BorderWidth + (arrowAtTop ? 0 : ah) + 2);
    updateGeometry();
    sh = sizeHint();
    int ml, mr, mt, mb;
    QSize sz = sizeHint();
    if (!arrowAtTop)
    {
        ml = mt = 0;
        mr = sz.width() - 1;
        mb = sz.height() - ah - 1;
    }
    else
    {
        ml = 0;
        mt = ah;
        mr = sz.width() - 1;
        mb = sz.height() - 1;
    }
    QPainterPath path;
    path.moveTo(ml + rc, mt);
    if (arrowAtTop && arrowAtLeft)
    {
        if (m_parameter.showArrow)
        {
            path.lineTo(ml + ao, mt);
            path.lineTo(ml + ao, mt - ah);
            path.lineTo(ml + ao + aw, mt);
        }
        move(qMax(pos.x() - ao, screenRect.left() + 2), pos.y());
    }
    else if (arrowAtTop && !arrowAtLeft)
    {
        if (m_parameter.showArrow)
        {
            path.lineTo(mr - ao - aw, mt);
            path.lineTo(mr - ao, mt - ah);
            path.lineTo(mr - ao, mt);
        }
        move(qMin(pos.x() - sh.width() + ao, screenRect.right() - sh.width() - 2), pos.y());
    }
    path.lineTo(mr - rc, mt);
    path.arcTo(QRect(mr - rc * 2, mt, rc * 2, rc * 2), 90, -90);
    path.lineTo(mr, mb - rc);
    path.arcTo(QRect(mr - rc * 2, mb - rc * 2, rc * 2, rc * 2), 0, -90);
    if (!arrowAtTop && !arrowAtLeft)
    {
        if (m_parameter.showArrow)
        {
            path.lineTo(mr - ao, mb);
            path.lineTo(mr - ao, mb + ah);
            path.lineTo(mr - ao - aw, mb);
        }
        move(qMin(pos.x() - sh.width() + ao, screenRect.right() - sh.width() - 2),
            pos.y() - sh.height());
    }
    else if (!arrowAtTop && arrowAtLeft)
    {
        if (m_parameter.showArrow)
        {
            path.lineTo(ao + aw, mb);
            path.lineTo(ao, mb + ah);
            path.lineTo(ao, mb);
        }
        move(qMax(pos.x() - ao, screenRect.x() + 2), pos.y() - sh.height());
    }
    path.lineTo(ml + rc, mb);
    path.arcTo(QRect(ml, mb - rc * 2, rc * 2, rc * 2), -90, -90);
    path.lineTo(ml, mt + rc);
    path.arcTo(QRect(ml, mt, rc * 2, rc * 2), 180, -90);
    return path;
}

}   // end namespace gcmp
